Skip to content

Add ⌘P worktree switcher (VS Code-style) with worktree MRU and direct-beats-fuzzy ranking#370

Closed
COCPORN wants to merge 1 commit into
supabitapp:mainfrom
COCPORN:pr/project-switcher
Closed

Add ⌘P worktree switcher (VS Code-style) with worktree MRU and direct-beats-fuzzy ranking#370
COCPORN wants to merge 1 commit into
supabitapp:mainfrom
COCPORN:pr/project-switcher

Conversation

@COCPORN

@COCPORN COCPORN commented May 30, 2026

Copy link
Copy Markdown
Contributor

Closes #592

What

Adds a ⌘P worktree switcher (VS Code-style quick-open) and moves the existing Command Palette to ⌘⇧P.

Why this binding

  • ⌘⇧P → Command Palette — exact VS Code match.
  • ⌘P → fast navigation — VS Code's ⌘P jumps between files; here it jumps between worktrees.

Both are rebindable or disablable in Settings → Keyboard Shortcuts → General, so this only changes the defaults.

Behavior

  • ⌘P opens a fuzzy switcher of worktrees, MRU-ordered. The current worktree is shown, but the default selection lands on the previous one — ⌘P then Enter is a Cmd+Tab-style toggle. Typing snaps to the top fuzzy match.
  • Rows read as a hierarchy: worktree name as the title, repo name as a quieter subtitle. The scorer matches both, so a query hits either the worktree or the project name.
  • MRU is recorded on both navigation paths (sidebar clicks and hotkeys/palette), falling back to main/first.
  • Cancelling the palette refocuses the current terminal.

Ranking — fuzzy search fixed

The fuzzy search that misbehaved in the earlier iteration of this PR is fixed. The shared scorer now guarantees direct beats fuzzy: a contiguous prefix/substring hit (including a clean repo-subtitle hit) always outranks a scattered subsequence hit — tiers are clamped so per-character bonuses can't bleed across a tier boundary. Recency (MRU) is lifted above match-spread so equal-quality matches resolve to the most-recently-used row. Pinned by directSubtitleMatchOutranksScatteredTitleMatch and contiguousSubstringOutranksScatteredSubsequence.

Tests

CommandPaletteFeatureTests + RepositoriesFeatureWorktreeMRUTests — switcher ordering/flagging, defaultIndex seeding, sidebar-path MRU, and the ranking tiers. This PR's CI runs make test.

Note on the extra commits

This branch also carries two fork-only CI commits (Skip Tuist auth on forks, Add fork-build CI…) that let the fork build an installable artifact where local builds are impossible. Happy to split them out if you'd rather keep this PR switcher-only.

Disclosure

Implemented with AI assistance (Claude Code). Scorer ranking logic verified locally; the full build + test suite runs via this PR's CI.

Adds a project switcher on ⌘P and moves the existing Command Palette to ⌘⇧P,
aligning with VS Code (⌘⇧P = Command Palette; ⌘P = fast navigation). Both
shortcuts remain user-rebindable via shortcut overrides.

- ⌘P opens a fuzzy switcher of repositories ordered most-recently-used. The
  current project is rendered (so you see where you are) but flagged
  `isCurrentProject` so the default selection lands on the *previous* project —
  ⌘P then Enter is a Cmd+Tab-style toggle. Typing snaps to the top fuzzy match.
- Switching to a project resolves to the worktree you last had open there
  (`lastWorktreeByProject`), falling back to main/first. The MRU is recorded on
  BOTH navigation paths — `setSingleWorktreeSelection` (hotkeys/palette) and
  `reduceSelectionChangedEffect` (sidebar clicks) — so it works however you
  navigate.
- New `PaletteMode` (.commands / .projectSwitcher), `selectProject` item/
  delegate, and a `dismissedWithoutSelection` delegate so cancelling the palette
  refocuses the current terminal.
- Command Palette hint text updated ⌘P → ⌘⇧P.

Covered by CommandPaletteFeatureTests and RepositoriesFeatureProjectMRUTests
(switcher ordering/flagging, defaultIndex seeding, sidebar-path MRU recording).

Implemented with AI assistance (Claude Code); reviewed and verified in a real
build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@COCPORN

COCPORN commented May 30, 2026

Copy link
Copy Markdown
Contributor Author

I've made a quick video showcasing the changes. Let me know if you're interested, I'll turn it into two PRs, if not I'm happy to just run off this private fork going forward.

The terminal-focus I actually consider a bug, I'd try to patch that even if you don't care for the project switcher.

Thanks for considering it.

Supacode.PR.Project.Swap.and.Terminal.Focus-web.mp4

@sbertix

sbertix commented May 30, 2026

Copy link
Copy Markdown
Collaborator

Genuinely loving this ❤️ thanks
(I'll try to review it asap)

@sbertix

sbertix commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

So I've played a bit more with this, and I feel like the use case for this, exactly as implemented, relies too much on the assumption from the video that most "projects" only have one worktree. It becomes almost useless for agentic-driven multi-worktree scenarios, which tend to be the main use case for Supacode.

I think the idea still has its merits, though, and I'd be more than happy to merge the two concerns, basically translating this into a palette for worktree selection history plus fuzzy search for everything not in history. Wdyt? @COCPORN

@COCPORN

COCPORN commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

I am not sure exactly what you mean, I don't really understand how the extra navigation palette interferes with worktrees. I'll probably just be running off my own fork going forward, because I actually need some other features that I'm fairly certain(?) won't be merged, so I am going to have to go off non mainline code personally anyway.

Thanks for considering it!

@sbertix

sbertix commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

I don't really understand how the extra navigation palette interferes with worktrees.

It doesn't. But the main use case for Supacode is:

  • repo A:
    • worktree 1
    • worktree 2
    • worktree 3
  • repo B:
    • worktree 4
    • worktree 5
    • worktree 6

If I go through W1, W3, W5, W4, it collapses to just the latest from A and the latest from B.
The case for this, without changes, works best specifically for the cases you're showing in the video, which are significantly less common:

  • repo A: worktree 1
  • repo B: worktree 2
  • repo C: worktree 3
  • repo D: worktree 4

My suggestion was just to update this so worktrees are the main rows in the palette, not "projects". It'd return the same exact result for single-worktree scenarios like the one you're highlighting, but provide the full picture for every other case. And I said it specifically because I want to merge this, because I feel like it's the right abstraction, it just needs to expand to cover more use cases, instead of just a personal one.

because I actually need some other features that I'm fairly certain(?) won't be merged

I'm always open to evaluating and discussing stuff. If you open an issue you'll know for sure how I feel about it, instead of guessing 😅

Thanks for considering it!

I'm not just considering this. I'm 100% merging this cause you had a great idea with this 🙇‍♂️. But if you're not interested in expanding it, I can take it from here.

@COCPORN

COCPORN commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

Ah, I see what you're saying. I think. You want the worktree-switch to also be added to the stack, so the quick-switching includes flipping between them with the keyboard?

Also being able to address the worktree directly somehow in the navigation palette by fuzzy-finding it on worktree-name combined with the project name?

Are we on the same page? Because if we are, I think this makes a LOT of sense, and I'm happy to implement that.

EDIT: I'll make a PR for the other feature when it lands with an accompanying video.

@sbertix

sbertix commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

Yep. Precisely that 💪

@Mpdreamz

Copy link
Copy Markdown

Just a drive by comment: looking forward to this feature!

@ryanb

ryanb commented Jun 29, 2026

Copy link
Copy Markdown

I would love to see a worktree navigation palette with the most recently accessed worktrees showing up first. I have a custom WezTerm setup which does this and it's the primary thing keeping me from switching to Supacode.

Keep up the great work!

@COCPORN

COCPORN commented Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

Quick update — I've reworked this exactly as @sbertix suggested: worktrees are now the primary rows (MRU-ordered), so it covers the multi-worktree case rather than assuming one worktree per repo. @ryanb that also gives you the most-recently-accessed-worktree-first navigation you described 👍

I'm going to temporarily close this while I polish the ⌘P search ranking — right now a scattered fuzzy match can outrank a direct substring match, and recency isn't weighted strongly enough for a navigation surface. Once that feels right I'll reopen with the refreshed worktree-switcher version. Thanks for the patience.

@COCPORN COCPORN closed this Jun 29, 2026
@sbertix

sbertix commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Thank you so much 🙇‍♂️

@COCPORN COCPORN changed the title Add ⌘P project switcher (VS Code-style) with per-project worktree memory Add ⌘P worktree switcher (VS Code-style) with worktree MRU and direct-beats-fuzzy ranking Jul 7, 2026
@github-actions github-actions Bot added the invalid Does not meet the contribution policy; closed automatically after a few days if left inactive. label Jul 7, 2026
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

Thanks for the pull request. It doesn't meet the contribution policy yet, so I've labeled it invalid. Please address the following, then push an update:

  • A commit is authored or co-authored by an AI agent. A human must be the author of record: re-author the commits under your own name (reset the author, or drop the Co-authored-by: trailer). Using AI tools is welcome, and you are encouraged to disclose them in the description.

Nothing needs to be redone. A pull request left invalid is closed automatically after a few days of inactivity. See CONTRIBUTING.md for the full flow.

@sbertix

sbertix commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the pull request. It doesn't meet the contribution policy yet, so I've labeled it invalid. Please address the following, then push an update.

Don't worry about this, by the way. It can be bypassed 100% if you want to keep the commit history. Not an issue at all 🙇‍♂️

COCPORN pushed a commit to COCPORN/supacode that referenced this pull request Jul 8, 2026
Adds a worktree switcher on ⌘P and moves the existing Command Palette to ⌘⇧P,
aligning with VS Code (⌘⇧P = Command Palette; ⌘P = fast navigation). Both
shortcuts remain user-rebindable via shortcut overrides.

Reworked from the original project switcher per the PR supabitapp#370 review: Supacode's
core use case is multi-worktree-per-repo, where a project-level switcher
collapses the whole history down to one entry per repo and loses the worktree
you actually had open. Worktrees are the primary rows instead.

- ⌘P opens a fuzzy switcher of every worktree, ordered most-recently-used
  (RepositoriesFeature.State.worktreeMRU). The current worktree is rendered (so
  you see where you are) but flagged `isCurrentWorktree` so the default
  selection lands on the *previous* worktree — ⌘P then Enter is a Cmd+Tab-style
  toggle. Typing fuzzy-matches the combined `repo / worktree` title, so a query
  hits either the project name or the worktree name.
- worktreeMRU is recorded on BOTH navigation paths — setSingleWorktreeSelection
  (hotkeys/palette) and reduceSelectionChangedEffect (sidebar clicks) — so it
  tracks however you navigate. Single-worktree repos behave exactly as the
  original project switcher did.
- New PaletteMode (.commands / .worktreeSwitcher). The switcher emits
  `selectWorktree` directly (no project→worktree resolution step), so activation
  lands focus in the chosen terminal; cancelling refocuses the current terminal.

Covered by CommandPaletteFeatureTests (switcher ordering/flagging, MRU sort,
combined title, mode dispatch) and RepositoriesFeatureWorktreeMRUTests (MRU
recording on both nav paths).

Implemented with AI assistance (Claude Code).
sbertix added a commit that referenced this pull request Jul 12, 2026
…-beats-fuzzy ranking (#608)

* Add ⌘P worktree switcher (VS Code-style) with worktree MRU

Adds a worktree switcher on ⌘P and moves the existing Command Palette to ⌘⇧P,
aligning with VS Code (⌘⇧P = Command Palette; ⌘P = fast navigation). Both
shortcuts remain user-rebindable via shortcut overrides.

Reworked from the original project switcher per the PR #370 review: Supacode's
core use case is multi-worktree-per-repo, where a project-level switcher
collapses the whole history down to one entry per repo and loses the worktree
you actually had open. Worktrees are the primary rows instead.

- ⌘P opens a fuzzy switcher of every worktree, ordered most-recently-used
  (RepositoriesFeature.State.worktreeMRU). The current worktree is rendered (so
  you see where you are) but flagged `isCurrentWorktree` so the default
  selection lands on the *previous* worktree — ⌘P then Enter is a Cmd+Tab-style
  toggle. Typing fuzzy-matches the combined `repo / worktree` title, so a query
  hits either the project name or the worktree name.
- worktreeMRU is recorded on BOTH navigation paths — setSingleWorktreeSelection
  (hotkeys/palette) and reduceSelectionChangedEffect (sidebar clicks) — so it
  tracks however you navigate. Single-worktree repos behave exactly as the
  original project switcher did.
- New PaletteMode (.commands / .worktreeSwitcher). The switcher emits
  `selectWorktree` directly (no project→worktree resolution step), so activation
  lands focus in the chosen terminal; cancelling refocuses the current terminal.

Covered by CommandPaletteFeatureTests (switcher ordering/flagging, MRU sort,
combined title, mode dispatch) and RepositoriesFeatureWorktreeMRUTests (MRU
recording on both nav paths).

Implemented with AI assistance (Claude Code).

* Assert worktreeMRU in selection/history TestStore tests

The worktree switcher records every concrete selection into the new
`worktreeMRU` state on both nav paths (setSingleWorktreeSelection and
reduceSelectionChangedEffect). TestStore checks state exhaustively, so the 27
selection/history tests that drive those paths now see an unasserted change.
Assert the new MRU head in each — selecting worktree W prepends W; nil/archived
selections leave it untouched (those tests already pass and are unchanged).

Behavior change is intentional (the switcher's whole point), so the tests follow.

* Polish ⌘P switcher: mode-aware placeholder + worktree/repo hierarchy

Two bits of feedback from running the build:

- The query placeholder was hardcoded "Search for actions or branches…" — stale
  now that ⌘P is worktree-only (actions live on ⌘⇧P). Make it mode-aware:
  "Go to worktree…" for the switcher, the actions wording for the command palette.
- Switcher rows rendered one flat `repo / worktree` string at a single weight, so
  the worktree name was hard to pick out from the repo. Split them: worktree name
  is the title, repo is the secondary subtitle (VS Code "filename big, path small").
  The fuzzy scorer matches title and subtitle, so a query still hits either name.
  Folder rows keep the repo name alone (no redundant subtitle).

The ⌘⇧P command palette is unchanged (still the flat `repo / worktree` rows
alongside actions).

* Fix ⌘P search ranking: direct matches beat fuzzy, recency weighs in

The shared CommandPaletteFuzzyScorer had two flaws the worktree switcher
surfaced:

- No tier between "prefix" and "scattered fuzzy", so a contiguous substring
  hit and a scattered subsequence hit landed in the same band — and the
  per-character position-0/separator bonuses could push a scattered match
  past a mid-word direct match. Added a contiguous-substring tier (title) and
  a direct (prefix/substring) tier for the subtitle, with every in-tier score
  clamped so it can never bleed into the tier above. Net: a direct match —
  including a clean repo (subtitle) hit — always outranks a scattered fuzzy
  one. This also undoes the regression from splitting switcher rows into
  worktree-title + repo-subtitle, where a fuzzy worktree hit buried an exact
  repo hit (subtitle matches previously scored in a no-threshold low band).

- Recency (MRU) was a near-last tiebreaker, below match-spread. Lifted it
  above matchDistance so once match quality (the score tier) is equal, the
  most-recently-used row wins — the right default for a navigation surface.

Pinned with directSubtitleMatchOutranksScatteredTitleMatch and
contiguousSubstringOutranksScatteredSubsequence (both fail on the old scorer).

* Skip Tuist auth on forks so fork CI can build

The `setup-macos` Tuist auth step was gated to skip only cross-fork PRs, so an
intra-fork PR (or a push to a fork's main) still tried `tuist auth login` and
failed with `notAuthenticated` / "No projects linked to the repository" — the
fork isn't linked in the Tuist dashboard. That blocked the whole build job
before lint/build/test ran.

Tuist auth is OIDC against the canonical upstream project and only buys the
remote cache; `tuist generate` (and thus the build) works without it — the
cross-fork-PR path already relies on this. Gate auth on the upstream repo so
forks (and cross-fork PRs) skip it and can run the full build+test job.

* Add fork-build CI to upload an installable Debug app artifact

Local builds are impossible on this Mac (Zig/libSystem), and test.yml builds but
never uploads the app, so a fork has no way to get a runnable build. Add a
`make package-app` target (zips the Debug build via the same -showBuildSettings
path resolution as run-app/install-dev-build) and a `fork-build.yml` workflow on
feat/** | fix/** | exp/** that builds, packages, and uploads `supacode-<sha>`.
No tests, no signing — just a downloadable .app.zip for local testing.

* Finish the ⌘P worktree switcher: glass palette, sidebar-styled rows, MRU fixes

Render the worktree switcher in the glass command-palette panel and keep ⌘⇧P
listing actions only, since worktree navigation is the switcher's job.

Style each switcher row like the sidebar: tint the worktree title and repo
subtitle from the sidebar section and row colors, show a distinct wifi badge
for a remote host, resolve folder rows to their custom name (falling back to
the row's own name) with no subtitle, and draw the sidebar's leading glyph
(branch or pull-request icon, folder, or missing) plus its CI check badge at
the sidebar's resting opacity. A pull request whose head branch no longer
matches the worktree falls back to the branch glyph.

Promote a direct subtitle match above a scattered title match in the fuzzy
scorer so an exact repo-name hit outranks a fuzzy worktree-name hit.

Keep the worktree MRU correct: prune it in the shared removeWorktree primitive
so every delete path drops the entry, keep transient pending-creation ids out
of it, and cap it at the history-stack limit, so a reused path can't inherit a
removed worktree's rank.

Pass the active mode to the panel's keyboard-navigation filter so arrow keys
and ⌘1-⌘5 drive the switcher rows, and force the command palette mode for
Ghostty's toggle action so it never reopens the last-used surface.

---------

Co-authored-by: Lars Thomas Denstad <ungedenstad@gmail.com>
Co-authored-by: Stefano Bertagno <stefano@bertagno.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

invalid Does not meet the contribution policy; closed automatically after a few days if left inactive.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Recent picker for worktrees and tabs (JetBrains "Recent Files"-style, recency-ordered switcher)

5 participants